package com.boarsoft.flow.common.wrapper;

import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSONObject;
import com.boarsoft.common.Util;
import com.boarsoft.common.util.JsonUtil;
import com.boarsoft.flow.common.bean.CommonWrapConfInfo;
import com.boarsoft.flow.common.bean.CommonWrapHandlderConfInfo;
import com.boarsoft.flow.common.bean.CommonWrapMethodConfInfo;
import com.boarsoft.flow.common.service.CommonWrapConfService;
import com.boarsoft.flow.core.bean.WrapHandler;

public class CommonWrapHandler implements WrapHandler {
    private final static Logger log = LoggerFactory.getLogger(CommonWrapHandler.class);

    private static final String REGEX = "\\.";
    private static final String POINT = ".";
    private static final ConcurrentHashMap<String, Class<?>> CLAZZ_MAP = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, List<CommonWrapConfInfo>> wrapConfMap = new ConcurrentHashMap<>();
    private CommonWrapConfService commonWrapConfService;
    private String key;

    @Override
    public Object checkIn(String entry, Object flowData) {
        String json = commonWrapConfService.getWrapConfInfo(key);
        log.debug("key=".concat(key));
        log.debug("json=".concat(json));
        parseJson(json);

        StringBuffer sb = new StringBuffer();
        sb.append(key).append("_").append("checkIn");

        List<CommonWrapConfInfo> wrapConfList = wrapConfMap.get(sb.toString());
        if (CollectionUtils.isEmpty(wrapConfList)) {
            return flowData;
        }

        Object obj = null;
        try {
            Class<?> toClazz = null;

            String toType = wrapConfList.get(0).getToType().trim();

            if (CLAZZ_MAP.containsKey(toType)) {
                toClazz = CLAZZ_MAP.get(toType);
            } else {
                toClazz = Class.forName(wrapConfList.get(0).getToType());
                CLAZZ_MAP.put(toType, toClazz);
            }

            obj = toClazz.newInstance();

            Object orderItemInfo = getAttrVal(flowData.getClass(), "orderItemInfo", "this", flowData);

            doMappingFromJsonObj(obj, JSONObject.parseObject(String.valueOf(orderItemInfo)), wrapConfList);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        log.debug("obj=".concat(obj.toString()));
        log.debug("flowData=".concat(flowData.toString()));

        return obj;
    }

    @Override
    public Object checkOut(Object nodeData, Object flowData) {
        StringBuffer sb = new StringBuffer();
        sb.append(key).append("_").append("checkOut");

        log.debug("nodeData=".concat(nodeData.toString()));

        List<CommonWrapConfInfo> wrapConfList = wrapConfMap.get(sb.toString());
        if (CollectionUtils.isEmpty(wrapConfList)) {
            return nodeData;
        }

        try {
            doMapping(nodeData, flowData, wrapConfList);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        log.debug("nodeData=".concat(nodeData.toString()));
        log.debug("flowData=".concat(flowData.toString()));

        return nodeData;
    }

    private void doMappingFromJsonObj(Object nodeData, JSONObject jsonObject,
                                      List<CommonWrapConfInfo> wrapConfList) throws Throwable {
        for (CommonWrapConfInfo info : wrapConfList) {

            if (StringUtils.isEmpty(info.getToObj())
                    || StringUtils.isEmpty(info.getToProp())
                    || StringUtils.isEmpty(info.getToType())
                    || StringUtils.isEmpty(info.getToPropType())) {
                continue;
            }

            Object val = jsonObject.get(info.getFromProp());
            setAttrVal(nodeData.getClass(), info.getToProp(), info.getToObj(), nodeData, val);
        }
    }

    private void doMapping(Object nodeData, Object flowData, List<CommonWrapConfInfo> wrapConfList) throws Throwable {
        Class<?> fromClazz = null;

        for (CommonWrapConfInfo info : wrapConfList) {
            String fromType = info.getFromType().trim();

            if (CLAZZ_MAP.containsKey(fromType)) {
                fromClazz = CLAZZ_MAP.get(fromType);
            } else {
                fromClazz = Class.forName(fromType);
                CLAZZ_MAP.put(fromType, fromClazz);
            }

            if (StringUtils.isEmpty(info.getFromObj())
                    || StringUtils.isEmpty(info.getFromProp())
                    || StringUtils.isEmpty(info.getFromType())
                    || StringUtils.isEmpty(info.getFromPropType())) {
                continue;
            }

            if (StringUtils.isEmpty(info.getToObj())
                    || StringUtils.isEmpty(info.getToProp())
                    || StringUtils.isEmpty(info.getToType())
                    || StringUtils.isEmpty(info.getToPropType())) {
                continue;
            }

            Object val = getAttrVal(fromClazz, info.getFromProp(), info.getFromObj(), flowData);
            setAttrVal(nodeData.getClass(), info.getToProp(), info.getToObj(), nodeData, val);
        }
    }

    private Object getAttrVal(Class<?> clazz, String attr, String str, Object obj) throws Throwable {
        if (str.contains(POINT)) {
            String[] arrays = str.split(REGEX);
            Field field = clazz.getDeclaredField(arrays[1]);
            field.setAccessible(true);

            String subStr = str.substring(str.indexOf(POINT) + 1);

            return getAttrVal(field.getType(), attr, subStr, field.get(obj));
        } else {
            Field field = clazz.getDeclaredField(attr);
            field.setAccessible(true);

            return field.get(obj);
        }
    }

    private void setAttrVal(Class<?> clazz, String attr, String str, Object trgtObj, Object attrVal) throws Throwable {
        if (str.contains(POINT)) {
            String[] arrays = str.split(REGEX);
            Field field = clazz.getDeclaredField(arrays[1]);
            field.setAccessible(true);

            Object obj = field.get(trgtObj);
            if (null == obj) {
                obj = field.getType().newInstance();
                field.set(trgtObj, obj);
            }

            String subStr = str.substring(str.indexOf(POINT) + 1);

            setAttrVal(field.getType(), attr, subStr, obj, attrVal);
        } else {
            Field field = clazz.getDeclaredField(attr);
            field.setAccessible(true);

            field.set(trgtObj, attrVal);
        }
    }

    private void parseJson(String jsonStr) {
        if (Util.strIsEmpty(jsonStr)) {
            return;
        }

        CommonWrapHandlderConfInfo commonWrapHandlderConfInfo =
                JsonUtil.parseObject(jsonStr, CommonWrapHandlderConfInfo.class);

        List<CommonWrapMethodConfInfo> commonWrapMethodConfInfoList =
                commonWrapHandlderConfInfo.getCommonWrapMethodConfInfoList();

        if (CollectionUtils.isEmpty(commonWrapMethodConfInfoList)) {
            return;
        }

        for (CommonWrapMethodConfInfo commonWrapMethodConfInfo : commonWrapMethodConfInfoList) {
            List<CommonWrapConfInfo> wrapConfInfoList = commonWrapMethodConfInfo.getCommonWrapConfInfoList();
            StringBuffer sb = new StringBuffer();
            sb.append(commonWrapHandlderConfInfo.getKey()).append("_").append(commonWrapMethodConfInfo.getMethodName());
            wrapConfMap.put(sb.toString(), wrapConfInfoList);
        }
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public CommonWrapConfService getCommonWrapConfService() {
        return commonWrapConfService;
    }

    public void setCommonWrapConfService(CommonWrapConfService commonWrapConfService) {
        this.commonWrapConfService = commonWrapConfService;
    }
}
